package health_system.health_system.service.impl;

import health_system.health_system.dto.*;
import health_system.health_system.entity.Moment;
import health_system.health_system.entity.MomentMedia;
import health_system.health_system.entity.User;
import health_system.health_system.exception.BusinessException;
import health_system.health_system.exception.MomentNotFoundException;
import health_system.health_system.exception.ResourceNotFoundException;
import health_system.health_system.repository.MomentMediaRepository;
import health_system.health_system.repository.MomentRepository;
import health_system.health_system.repository.UserRepository;
import health_system.health_system.service.MomentService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class MomentServiceImpl implements MomentService {

    private final MomentRepository momentRepository;
    private final UserRepository userRepository;
    private final MomentMediaRepository momentMediaRepository;

    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = "moments", allEntries = true),
        @CacheEvict(value = "userMoments", allEntries = true),
        @CacheEvict(value = "visibleMoments", allEntries = true)
    })
    public MomentDTO createMoment(MomentCreateDTO createDTO) {
        log.debug("Creating moment for user ID: {}", createDTO.getUserId());
        
        User user = userRepository.findById(createDTO.getUserId())
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", createDTO.getUserId()));
        
        Moment moment = new Moment();
        moment.setUser(user);
        moment.setContent(createDTO.getContent());
        moment.setVisibility(createDTO.getVisibility());
        moment.setVisibilityDuration(createDTO.getVisibilityDuration() != null ? 
                createDTO.getVisibilityDuration() : 0);
        
        // If visibility is CUSTOM, set visible to users
        if (createDTO.getVisibility() == Moment.VisibilityType.CUSTOM && 
                createDTO.getVisibleToUserIds() != null && !createDTO.getVisibleToUserIds().isEmpty()) {
            Set<User> visibleToUsers = createDTO.getVisibleToUserIds().stream()
                    .map(id -> userRepository.findById(id)
                            .orElseThrow(() -> new ResourceNotFoundException("User", "id", id)))
                    .collect(Collectors.toSet());
            moment.setVisibleToUsers(visibleToUsers);
        }
        
        try {
            Moment savedMoment = momentRepository.save(moment);
            
            // Save media if provided
            if (createDTO.getMediaList() != null && !createDTO.getMediaList().isEmpty()) {
                List<MomentMedia> mediaList = createDTO.getMediaList().stream()
                        .map(mediaDTO -> {
                            MomentMedia media = new MomentMedia();
                            media.setMoment(savedMoment);
                            media.setMediaUrl(mediaDTO.getMediaUrl());
                            media.setMediaType(mediaDTO.getMediaType());
                            media.setOrderIndex(mediaDTO.getOrderIndex());
                            media.setThumbnailUrl(mediaDTO.getThumbnailUrl());
                            media.setDuration(mediaDTO.getDuration());
                            return media;
                        })
                        .collect(Collectors.toList());
                
                List<MomentMedia> savedMedia = momentMediaRepository.saveAll(mediaList);
                savedMoment.setMediaList(new HashSet<>(savedMedia));
            }
            
            log.info("Successfully created moment with ID: {}", savedMoment.getId());
            return MomentDTO.fromEntity(savedMoment);
        } catch (DataIntegrityViolationException e) {
            log.error("Failed to create moment due to data integrity violation", e);
            throw new BusinessException("Failed to create moment. Please check your input data.", e);
        }
    }

    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = "moments", key = "#id"),
        @CacheEvict(value = "userMoments", allEntries = true),
        @CacheEvict(value = "visibleMoments", allEntries = true)
    })
    public MomentDTO updateMoment(Long id, MomentUpdateDTO updateDTO) {
        log.debug("Updating moment with ID: {}", id);
        
        Moment moment = getMomentEntityById(id);
        
        // Update content if provided
        if (updateDTO.getContent() != null) {
            moment.setContent(updateDTO.getContent());
        }
        
        // Update visibility settings if provided
        if (updateDTO.getVisibility() != null) {
            moment.setVisibility(updateDTO.getVisibility());
        }
        
        if (updateDTO.getVisibilityDuration() != null) {
            moment.setVisibilityDuration(updateDTO.getVisibilityDuration());
        }
        
        // Update visible to users if visibility is CUSTOM and the list is provided
        if (moment.getVisibility() == Moment.VisibilityType.CUSTOM && 
                updateDTO.getVisibleToUserIds() != null) {
            Set<User> visibleToUsers = updateDTO.getVisibleToUserIds().stream()
                    .map(userId -> userRepository.findById(userId)
                            .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId)))
                    .collect(Collectors.toSet());
            moment.setVisibleToUsers(visibleToUsers);
        }
        
        // Remove media if specified
        if (updateDTO.getMediaIdsToRemove() != null && !updateDTO.getMediaIdsToRemove().isEmpty()) {
            Set<MomentMedia> remainingMedia = moment.getMediaList().stream()
                    .filter(media -> !updateDTO.getMediaIdsToRemove().contains(media.getId()))
                    .collect(Collectors.toSet());
            moment.setMediaList(remainingMedia);
            
            updateDTO.getMediaIdsToRemove().forEach(mediaId -> {
                if (momentMediaRepository.existsById(mediaId)) {
                    momentMediaRepository.deleteById(mediaId);
                }
            });
        }
        
        // Add new media if provided
        if (updateDTO.getNewMediaList() != null && !updateDTO.getNewMediaList().isEmpty()) {
            List<MomentMedia> newMediaList = updateDTO.getNewMediaList().stream()
                    .map(mediaDTO -> {
                        MomentMedia media = new MomentMedia();
                        media.setMoment(moment);
                        media.setMediaUrl(mediaDTO.getMediaUrl());
                        media.setMediaType(mediaDTO.getMediaType());
                        media.setOrderIndex(mediaDTO.getOrderIndex());
                        media.setThumbnailUrl(mediaDTO.getThumbnailUrl());
                        media.setDuration(mediaDTO.getDuration());
                        return media;
                    })
                    .collect(Collectors.toList());
            
            List<MomentMedia> savedMedia = momentMediaRepository.saveAll(newMediaList);
            moment.getMediaList().addAll(savedMedia);
        }
        
        try {
            Moment updatedMoment = momentRepository.save(moment);
            log.info("Successfully updated moment with ID: {}", updatedMoment.getId());
            return MomentDTO.fromEntity(updatedMoment);
        } catch (DataIntegrityViolationException e) {
            log.error("Failed to update moment with ID: {} due to data integrity violation", id, e);
            throw new BusinessException("Failed to update moment. Please check your input data.", e);
        }
    }

    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = "moments", key = "#id"),
        @CacheEvict(value = "userMoments", allEntries = true),
        @CacheEvict(value = "visibleMoments", allEntries = true)
    })
    public void deleteMoment(Long id) {
        log.debug("Deleting moment with ID: {}", id);
        
        // 获取朋友圈实体，包括媒体文件
        Moment moment = momentRepository.findById(id)
                .orElseThrow(() -> new MomentNotFoundException(id));
        
        // 删除关联的媒体文件
        if (moment.getMediaList() != null && !moment.getMediaList().isEmpty()) {
            for (MomentMedia media : moment.getMediaList()) {
                // 获取媒体文件路径
                String mediaUrl = media.getMediaUrl();
                if (mediaUrl != null && !mediaUrl.isEmpty()) {
                    try {
                        // 从URL中提取文件名
                        String fileName = extractFileNameFromUrl(mediaUrl);
                        if (fileName != null) {
                            // 构建文件完整路径
                            java.nio.file.Path filePath = java.nio.file.Paths.get("uploads/moments", fileName);
                            // 删除文件
                            if (java.nio.file.Files.exists(filePath)) {
                                java.nio.file.Files.delete(filePath);
                                log.info("Deleted media file: {}", filePath);
                            }
                        }
                    } catch (Exception e) {
                        log.error("Failed to delete media file for URL: {}", mediaUrl, e);
                        // 继续删除其他文件，不中断流程
                    }
                }
            }
        }
        
        try {
            momentRepository.deleteById(id);
            log.info("Successfully deleted moment with ID: {}", id);
        } catch (Exception e) {
            log.error("Failed to delete moment with ID: {}", id, e);
            throw new BusinessException("Failed to delete moment with ID: " + id, e);
        }
    }
    
    /**
     * 从媒体URL中提取文件名
     */
    private String extractFileNameFromUrl(String mediaUrl) {
        if (mediaUrl == null || mediaUrl.isEmpty()) {
            return null;
        }
        
        // 处理URL格式，例如：/api/moments/media/moment_20250509204335313_943.png
        String[] parts = mediaUrl.split("/");
        if (parts.length > 0) {
            return parts[parts.length - 1]; // 获取最后一部分作为文件名
        }
        
        return null;
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "moments", key = "#id")
    public MomentDTO getMomentById(Long id) {
        log.debug("Getting moment by ID: {}", id);
        
        Moment moment = getMomentEntityById(id);
        return MomentDTO.fromEntity(moment);
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentsWithLikeStatus", key = "#id + '-' + #currentUserId")
    public MomentDTO getMomentWithLikeStatus(Long id, Long currentUserId) {
        log.debug("Getting moment by ID: {} with like status for user ID: {}", id, currentUserId);
        
        Moment moment = getMomentEntityById(id);
        return MomentDTO.fromEntityWithLikeStatus(moment, currentUserId);
    }
    
    @Override
    @Transactional(readOnly = true)
    public Moment getMomentEntityById(Long id) {
        log.debug("Getting moment entity by ID: {}", id);
        
        return momentRepository.findById(id)
                .orElseThrow(() -> new MomentNotFoundException(id));
    }
    
    @Override
    @Transactional(readOnly = true)
    public Optional<Moment> findMomentEntityById(Long id) {
        log.debug("Finding moment entity by ID: {}", id);
        
        return momentRepository.findById(id);
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "userMoments", key = "#userId")
    public List<MomentDTO> getAllMomentsByUser(Long userId) {
        log.debug("Getting all moments by user ID: {}", userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        List<Moment> moments = momentRepository.findByUserOrderByCreatedAtDesc(user);
        
        if (moments.isEmpty()) {
            return Collections.emptyList();
        }
        
        return moments.stream()
                .map(MomentDTO::fromEntity)
                .collect(Collectors.toList());
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "userMomentsPaged", 
              key = "#userId + '-page:' + #pageable.pageNumber + '-size:' + #pageable.pageSize")
    public PageResponseDTO<MomentDTO> getAllMomentsByUser(Long userId, Pageable pageable) {
        log.debug("Getting paginated moments by user ID: {} with page: {}", 
                userId, pageable.getPageNumber());
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        Page<Moment> momentsPage = momentRepository.findByUserOrderByCreatedAtDesc(user, pageable);
        
        Page<MomentDTO> dtoPage = momentsPage.map(MomentDTO::fromEntity);
        
        return PageResponseDTO.fromPage(dtoPage);
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "visibleMoments", key = "#viewerId")
    public List<MomentDTO> getMomentsVisibleToUser(Long viewerId) {
        log.debug("Getting moments visible to user ID: {}", viewerId);
        
        User viewer = userRepository.findById(viewerId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", viewerId));
        
        List<Moment> moments = momentRepository.findMomentsVisibleToUser(viewer);
        
        if (moments.isEmpty()) {
            return Collections.emptyList();
        }
        
        return moments.stream()
                .map(MomentDTO::fromEntity)
                .collect(Collectors.toList());
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "visibleMomentsPaged", 
              key = "#viewerId + '-page:' + #pageable.pageNumber + '-size:' + #pageable.pageSize")
    public PageResponseDTO<MomentDTO> getMomentsVisibleToUser(Long viewerId, Pageable pageable) {
        log.debug("Getting paginated moments visible to user ID: {} with page: {}", 
                viewerId, pageable.getPageNumber());
        
        User viewer = userRepository.findById(viewerId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", viewerId));
        
        // Calculate a date for moments with visibility duration
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime sixMonthsAgo = now.minusMonths(6); // Assuming 6 months is the maximum visibility duration
        
        Page<Moment> momentsPage = momentRepository.findVisibleMomentsCreatedSince(viewer, sixMonthsAgo, pageable);
        
        Page<MomentDTO> dtoPage = momentsPage.map(MomentDTO::fromEntity);
        
        return PageResponseDTO.fromPage(dtoPage);
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "visibleMomentsWithLikeStatus", key = "#viewerId")
    public List<MomentDTO> getMomentsVisibleToUserWithLikeStatus(Long viewerId) {
        log.debug("Getting moments visible to user ID: {} with like status", viewerId);
        
        User viewer = userRepository.findById(viewerId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", viewerId));
        
        List<Moment> moments = momentRepository.findMomentsVisibleToUser(viewer);
        
        if (moments.isEmpty()) {
            return Collections.emptyList();
        }
        
        return moments.stream()
                .map(moment -> MomentDTO.fromEntityWithLikeStatus(moment, viewerId))
                .collect(Collectors.toList());
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "recommendedMoments", 
              key = "#viewerId + '-page:' + #pageable.pageNumber + '-size:' + #pageable.pageSize")
    public PageResponseDTO<MomentDTO> getRecommendedMomentsForUser(Long viewerId, Pageable pageable) {
        log.debug("Getting recommended moments for user ID: {} with page: {}", 
                viewerId, pageable.getPageNumber());
        
        User viewer = userRepository.findById(viewerId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", viewerId));
        
        Page<Moment> momentsPage = momentRepository.findRecommendedMomentsForUser(viewer, pageable);
        
        Page<MomentDTO> dtoPage = momentsPage.map(moment -> MomentDTO.fromEntityWithLikeStatus(moment, viewerId));
        
        return PageResponseDTO.fromPage(dtoPage);
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "visibleMomentsSince", 
              key = "#viewerId + '-since:' + #since + '-page:' + #pageable.pageNumber + '-size:' + #pageable.pageSize")
    public PageResponseDTO<MomentDTO> getMomentsVisibleToUserSince(
            Long viewerId, LocalDateTime since, Pageable pageable) {
        log.debug("Getting moments visible to user ID: {} since {} with page: {}", 
                viewerId, since, pageable.getPageNumber());
        
        User viewer = userRepository.findById(viewerId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", viewerId));
        
        Page<Moment> momentsPage = momentRepository.findVisibleMomentsCreatedSince(viewer, since, pageable);
        
        Page<MomentDTO> dtoPage = momentsPage.map(MomentDTO::fromEntity);
        
        return PageResponseDTO.fromPage(dtoPage);
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentsByDateRange", 
              key = "#userId + '-start:' + #startDate + '-end:' + #endDate")
    public List<MomentDTO> getMomentsByUserAndDateRange(
            Long userId, LocalDateTime startDate, LocalDateTime endDate) {
        log.debug("Getting moments by user ID: {} in date range from {} to {}", 
                userId, startDate, endDate);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        List<Moment> moments = momentRepository.findByUserAndDateRange(user, startDate, endDate);
        
        if (moments.isEmpty()) {
            return Collections.emptyList();
        }
        
        return moments.stream()
                .map(MomentDTO::fromEntity)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "userMomentsCount", key = "#userId")
    public Long countMomentsByUser(Long userId) {
        log.debug("Counting moments by user ID: {}", userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        return momentRepository.countByUser(user);
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentsByKeyword", key = "#keyword + '-viewer:' + #viewerId")
    public List<MomentDTO> searchMomentsByKeyword(String keyword, Long viewerId) {
        log.debug("Searching moments with keyword: {} visible to user ID: {}", keyword, viewerId);
        
        User viewer = userRepository.findById(viewerId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", viewerId));
        
        List<Moment> moments = momentRepository.searchMomentsByKeyword(keyword, viewer);
        
        if (moments.isEmpty()) {
            return Collections.emptyList();
        }
        
        return moments.stream()
                .map(moment -> MomentDTO.fromEntityWithLikeStatus(moment, viewerId))
                .collect(Collectors.toList());
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentStats", key = "#userId")
    public MomentStatsDTO getMomentStatsByUser(Long userId) {
        log.debug("Getting moment statistics for user ID: {}", userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        // Get all moments by the user
        List<Moment> moments = momentRepository.findByUserOrderByCreatedAtDesc(user);
        
        if (moments.isEmpty()) {
            return MomentStatsDTO.builder()
                    .userId(userId)
                    .username(user.getUsername())
                    .totalMomentCount(0L)
                    .publicMomentCount(0L)
                    .privateMomentCount(0L)
                    .friendsMomentCount(0L)
                    .customMomentCount(0L)
                    .totalLikeCount(0L)
                    .totalCommentCount(0L)
                    .totalShareCount(0L)
                    .avgLikesPerMoment(0.0)
                    .avgCommentsPerMoment(0.0)
                    .build();
        }
        
        // Calculate statistics
        long totalMomentCount = moments.size();
        
        // Count by visibility type
        long publicMomentCount = moments.stream()
                .filter(moment -> moment.getVisibility() == Moment.VisibilityType.PUBLIC)
                .count();
        
        long privateMomentCount = moments.stream()
                .filter(moment -> moment.getVisibility() == Moment.VisibilityType.PRIVATE)
                .count();
        
        long friendsMomentCount = moments.stream()
                .filter(moment -> moment.getVisibility() == Moment.VisibilityType.FRIENDS)
                .count();
        
        long customMomentCount = moments.stream()
                .filter(moment -> moment.getVisibility() == Moment.VisibilityType.CUSTOM)
                .count();
        
        // Count likes, comments, shares
        long totalLikeCount = moments.stream()
                .mapToLong(moment -> moment.getLikes() != null ? moment.getLikes().size() : 0)
                .sum();
        
        long totalCommentCount = moments.stream()
                .mapToLong(moment -> moment.getComments() != null ? moment.getComments().size() : 0)
                .sum();
        
        long totalShareCount = moments.stream()
                .mapToLong(moment -> moment.getShares() != null ? moment.getShares().size() : 0)
                .sum();
        
        // Calculate averages
        double avgLikesPerMoment = totalMomentCount > 0 ? (double) totalLikeCount / totalMomentCount : 0;
        double avgCommentsPerMoment = totalMomentCount > 0 ? (double) totalCommentCount / totalMomentCount : 0;
        
        // Find most used visibility
        Map<Moment.VisibilityType, Long> visibilityTypeCounts = moments.stream()
                .collect(Collectors.groupingBy(Moment::getVisibility, Collectors.counting()));
        
        Map.Entry<Moment.VisibilityType, Long> mostCommonEntry = visibilityTypeCounts.entrySet().stream()
                .max(Map.Entry.comparingByValue())
                .orElse(null);
        
        Moment.VisibilityType mostUsedVisibility = mostCommonEntry != null ? mostCommonEntry.getKey() : null;
        
        // Get last moment date
        LocalDateTime lastMomentDate = moments.isEmpty() ? null : moments.get(0).getCreatedAt();
        
        return MomentStatsDTO.builder()
                .userId(userId)
                .username(user.getUsername())
                .totalMomentCount(totalMomentCount)
                .publicMomentCount(publicMomentCount)
                .privateMomentCount(privateMomentCount)
                .friendsMomentCount(friendsMomentCount)
                .customMomentCount(customMomentCount)
                .totalLikeCount(totalLikeCount)
                .totalCommentCount(totalCommentCount)
                .totalShareCount(totalShareCount)
                .avgLikesPerMoment(avgLikesPerMoment)
                .avgCommentsPerMoment(avgCommentsPerMoment)
                .mostUsedVisibility(mostUsedVisibility)
                .lastMomentDate(lastMomentDate)
                .build();
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "userMomentActivity", key = "#userId")
    public UserMomentActivityDTO getUserMomentActivity(Long userId) {
        log.debug("Getting user moment activity for user ID: {}", userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        // Get all moments by the user
        List<Moment> moments = momentRepository.findByUserOrderByCreatedAtDesc(user);
        
        long momentsCreatedCount = moments.size();
        
        // This would ideally use dedicated repository methods for better performance
        // For now, we're implementing a simple version
        long likesGivenCount = 0; // This would be: momentLikeRepository.countByUser(user);
        long commentsWrittenCount = 0; // This would be: momentCommentRepository.countByUser(user);
        long momentsSharedCount = 0; // This would be: momentShareRepository.countByUser(user);
        
        LocalDateTime lastActivityDate = null;
        if (!moments.isEmpty()) {
            lastActivityDate = moments.get(0).getCreatedAt();
        }
        
        return UserMomentActivityDTO.builder()
                .userId(userId)
                .username(user.getUsername())
                .userAvatarUrl(user.getAvatarUrl())
                .momentsCreatedCount(momentsCreatedCount)
                .likesGivenCount(likesGivenCount)
                .commentsWrittenCount(commentsWrittenCount)
                .momentsSharedCount(momentsSharedCount)
                .lastActivityDate(lastActivityDate)
                .build();
    }
    
    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = "moments", key = "#momentId"),
        @CacheEvict(value = "visibleMoments", allEntries = true)
    })
    public void addUserToVisibilityList(Long momentId, Long userId) {
        log.debug("Adding user ID: {} to visibility list of moment ID: {}", userId, momentId);
        
        Moment moment = getMomentEntityById(momentId);
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        if (moment.getVisibility() != Moment.VisibilityType.CUSTOM) {
            moment.setVisibility(Moment.VisibilityType.CUSTOM);
        }
        
        if (moment.getVisibleToUsers() == null) {
            moment.setVisibleToUsers(new HashSet<>());
        }
        
        moment.getVisibleToUsers().add(user);
        momentRepository.save(moment);
        
        log.info("Successfully added user ID: {} to visibility list of moment ID: {}", userId, momentId);
    }
    
    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = "moments", key = "#momentId"),
        @CacheEvict(value = "visibleMoments", allEntries = true)
    })
    public void removeUserFromVisibilityList(Long momentId, Long userId) {
        log.debug("Removing user ID: {} from visibility list of moment ID: {}", userId, momentId);
        
        Moment moment = getMomentEntityById(momentId);
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        if (moment.getVisibility() != Moment.VisibilityType.CUSTOM || moment.getVisibleToUsers() == null) {
            log.warn("Moment ID: {} is not using CUSTOM visibility or has no visibility list", momentId);
            return;
        }
        
        moment.getVisibleToUsers().remove(user);
        momentRepository.save(moment);
        
        log.info("Successfully removed user ID: {} from visibility list of moment ID: {}", userId, momentId);
    }
    
    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = "moments", key = "#id"),
        @CacheEvict(value = "visibleMoments", allEntries = true)
    })
    public MomentDTO updateMomentVisibility(Long id, Moment.VisibilityType visibility, Set<Long> visibleToUserIds) {
        log.debug("Updating visibility of moment ID: {} to {}", id, visibility);
        
        Moment moment = getMomentEntityById(id);
        moment.setVisibility(visibility);
        
        // Clear and update visible users if needed
        if (visibility == Moment.VisibilityType.CUSTOM && visibleToUserIds != null && !visibleToUserIds.isEmpty()) {
            Set<User> visibleToUsers = visibleToUserIds.stream()
                    .map(userId -> userRepository.findById(userId)
                            .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId)))
                    .collect(Collectors.toSet());
            
            moment.setVisibleToUsers(visibleToUsers);
        } else if (visibility != Moment.VisibilityType.CUSTOM) {
            // Clear the visibility list for non-CUSTOM visibility types
            moment.setVisibleToUsers(new HashSet<>());
        }
        
        Moment updatedMoment = momentRepository.save(moment);
        log.info("Successfully updated visibility of moment ID: {} to {}", id, visibility);
        
        return MomentDTO.fromEntity(updatedMoment);
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "trendingMoments", key = "#viewerId + '-limit:' + #limit")
    public List<MomentDTO> getTrendingMoments(Long viewerId, int limit) {
        log.debug("Getting trending moments for user ID: {} with limit: {}", viewerId, limit);
        
        User viewer = userRepository.findById(viewerId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", viewerId));
        
        // In a real implementation, this would use a custom query with weights for likes, comments, and shares
        // For now, we'll use the recommended moments as a proxy for trending
        Page<Moment> momentsPage = momentRepository.findRecommendedMomentsForUser(
                viewer, Pageable.ofSize(limit));
        
        List<Moment> moments = momentsPage.getContent();
        
        if (moments.isEmpty()) {
            return Collections.emptyList();
        }
        
        return moments.stream()
                .map(moment -> MomentDTO.fromEntityWithLikeStatus(moment, viewerId))
                .collect(Collectors.toList());
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentsByIds", key = "#momentIds.hashCode() + '-viewer:' + #viewerId")
    public List<MomentDTO> getMomentsByIds(List<Long> momentIds, Long viewerId) {
        log.debug("Getting moments by IDs: {} for viewer ID: {}", momentIds, viewerId);
        
        if (momentIds == null || momentIds.isEmpty()) {
            return Collections.emptyList();
        }
        
        User viewer = userRepository.findById(viewerId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", viewerId));
        
        // In a real implementation, this would use a custom repository method
        // For now, we'll collect moments one by one
        List<MomentDTO> result = new ArrayList<>();
        
        for (Long momentId : momentIds) {
            try {
                Moment moment = getMomentEntityById(momentId);
                
                // Check visibility (simplified)
                boolean isVisible = false;
                
                if (moment.getVisibility() == Moment.VisibilityType.PUBLIC) {
                    isVisible = true;
                } else if (moment.getUser().getId().equals(viewerId)) {
                    isVisible = true;
                } else if (moment.getVisibility() == Moment.VisibilityType.CUSTOM && 
                        moment.getVisibleToUsers() != null && 
                        moment.getVisibleToUsers().stream().anyMatch(u -> u.getId().equals(viewerId))) {
                    isVisible = true;
                }
                // For FRIENDS visibility, we would need to check friendship status
                
                if (isVisible) {
                    result.add(MomentDTO.fromEntityWithLikeStatus(moment, viewerId));
                }
            } catch (MomentNotFoundException e) {
                log.warn("Moment with ID {} not found, skipping", momentId);
            }
        }
        
        return result;
    }
} 